#pragma once

#include "tokenizer.h"
#include <algorithm>
#include <istream>
#include <list>
#include <memory>
#include <ostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

// Value type in parser
enum class ValueType : std::int32_t {
  NONE = 0,
  STRUCT,
  STRING,
  VOID,
  I8,
  I16,
  I32,
  I64,
  UI8,
  UI16,
  UI32,
  UI64,
  FLOAT,
  DOUBLE,
  BOOL,
  SET,
  SEQ,
  DICT,
  ENUM,
};

// Common node for struct's field, method return type and method argment
struct IdlNode {
  IdlNode(const IdlNode &) = delete;
  const IdlNode &operator=(const IdlNode &) = delete;
  // ctor
  IdlNode() {
    type = ValueType::NONE;
    first = nullptr;
    second = nullptr;
  }
  // rvalue ctor
  IdlNode(IdlNode &&rht) noexcept {
    type = rht.type;
    token = rht.token;
    first = rht.first;
    second = rht.second;
    name.swap(rht.name);
    default_value.swap(rht.default_value);
    rht.first = nullptr;
    rht.second = nullptr;
  }
  // dtor
  ~IdlNode() {
    type = ValueType::NONE;
    if (first) {
      delete first;
    }
    if (second) {
      delete second;
    }
    first = nullptr;
    second = nullptr;
  }
  // rvalue assignment
  const IdlNode &operator=(IdlNode &&rht) noexcept {
    type = rht.type;
    token = rht.token;
    first = rht.first;
    second = rht.second;
    name.swap(rht.name);
    default_value.swap(rht.default_value);
    rht.first = nullptr;
    rht.second = nullptr;
    return *this;
  }
  ValueType type{ValueType::NONE}; ///< Value type
  Token token;                     ///< The associate token
  IdlNode *first{nullptr};         ///< extenstion
  IdlNode *second{nullptr};        ///< extension
  using StringPtr = std::unique_ptr<std::string>;
  StringPtr name;
  StringPtr default_value;
  ///< Validation
  operator bool() { return (type != ValueType::NONE); }
};

// Service Method
struct IdlMethod {
  IdlMethod(const IdlMethod &) = delete;
  const IdlMethod &operator=(const IdlMethod &) = delete;
  // ctor
  IdlMethod() {}
  // rvalue ctor
  IdlMethod(IdlMethod &&rht) noexcept {
    name = std::move(rht.name);
    retType = std::move(rht.retType);
    oneway = rht.oneway;
    event = rht.event;
    timeout = rht.timeout;
    retry = rht.retry;
    no_except = rht.no_except;
    arg_list = std::move(rht.arg_list);
    is_stream = rht.is_stream;
  }
  // rvalue assignment
  IdlMethod &operator=(IdlMethod &&rht) noexcept {
    name = std::move(rht.name);
    retType = std::move(rht.retType);
    oneway = rht.oneway;
    event = rht.event;
    timeout = rht.timeout;
    retry = rht.retry;
    no_except = rht.no_except;
    arg_list = std::move(rht.arg_list);
    is_stream = rht.is_stream;
    return *this;
  }
  std::string name;  ///< method name
  IdlNode retType;   ///< The return type of method
  int timeout{5000}; // Method timeout
  int retry{0};      // Max retry times
  static const int DEFAULT_MAX_RETRY = 3;
  std::list<IdlNode> arg_list; ///< Arguments
  bool oneway{false}; ///< One way method, just call and return immediatelly and
                      ///< only support return type of void
  bool event{false};  ///< Event method
  bool no_except{false}; ///< never throw exception
  bool is_stream{false}; ///< Is streaming method?
  // Validation
  operator bool() { return (!name.empty()); }
};

// Struct
struct IdlStruct {
  IdlStruct(const IdlStruct &) = delete;
  const IdlStruct &operator=(const IdlStruct &) = delete;
  // ctor
  IdlStruct() {}
  // rvalue ctor
  IdlStruct(IdlStruct &&rht) noexcept {
    name = std::move(rht.name);
    field_list = std::move(rht.field_list);
  }
  // rvalue assignment
  IdlStruct &operator=(IdlStruct &&rht) noexcept {
    name = std::move(rht.name);
    field_list = std::move(rht.field_list);
    return *this;
  }
  std::string name;              ///< struct name
  std::list<IdlNode> field_list; /// struct fields
  // Validation
  operator bool() { return (!name.empty()); }
  // Add a struct field
  bool addField(IdlNode &&field) {
    for (auto &node : field_list) {
      if (get_field_name(node) == get_field_name(field)) {
        return false;
      }
    }
    field_list.emplace_back(std::move(field));
    return true;
  }
  // Get field name
  const std::string &get_field_name(const IdlNode &node) {
    if (node.type == ValueType::STRUCT) {
      return node.first->token.getText();
    } else {
      return node.token.getText();
    }
  }
};

struct IdlEnumValue {
  std::string name;
  std::int32_t value;
};

// Enum
struct IdlEnum {
  IdlEnum(const IdlEnum &) = delete;
  const IdlEnum &operator=(const IdlEnum &) = delete;
  // ctor
  IdlEnum() {}
  // rvalue ctor
  IdlEnum(IdlEnum &&rht) noexcept {
    enum_vec = std::move(rht.enum_vec);
    name = std::move(rht.name);
    max_value = rht.max_value;
  }
  // rvalue assignment
  IdlEnum &operator=(IdlEnum &&rht) noexcept {
    enum_vec = std::move(rht.enum_vec);
    name = std::move(rht.name);
    max_value = rht.max_value;
    return *this;
  }
  std::int32_t getNextValue() {
    std::int32_t v = 0;
    for (const auto &i : enum_vec) {
      if (i.value > v) {
        v = i.value;
      }
    }
    return v + 1;
  }
  bool isValidValue(std::int32_t v) {
    for (const auto &i : enum_vec) {
      if (i.value == v) {
        return false;
      }
    }
    return true;
  }
  std::vector<IdlEnumValue> enum_vec; ///< enum vector
  std::string name;                   ///< enum name
  std::int32_t max_value{1};          ///< The maximum value of enum
};

enum class ServiceType {
  NONE = 0,
  MULTIPLE = 1,
  SINGLE,
  REENTRANT,
  GENERIC,
};

enum class ServiceLoadType {
  NONE = 0,
  STATIC = 1,
  DYNAMIC,
};

struct IdlServiceNotation {
  std::string key;
  std::vector<std::string> values;
};

// Service
struct IdlService {
  IdlService(const IdlService &) = delete;
  const IdlService &operator=(const IdlService &) = delete;
  // ctor
  IdlService() {}
  // rvalue ctor
  IdlService(IdlService &&rht) noexcept {
    name = std::move(rht.name);
    type = rht.type;
    fileName = std::move(rht.fileName);
    uuid = std::move(rht.uuid);
    method_list = std::move(rht.method_list);
    maxInst = rht.maxInst;
    loadType = rht.loadType;
    service_notations = std::move(rht.service_notations);
  }
  // rvalue assignment
  IdlService &operator=(IdlService &&rht) noexcept {
    name = std::move(rht.name);
    type = rht.type;
    fileName = std::move(rht.fileName);
    uuid = std::move(rht.uuid);
    method_list = std::move(rht.method_list);
    maxInst = rht.maxInst;
    loadType = rht.loadType;
    service_notations = std::move(rht.service_notations);
    return *this;
  }
  std::string name;                    // Service name
  ServiceType type{ServiceType::NONE}; // Service type
  std::string fileName;                // The file which service belong to
  std::string uuid;                    // Service UUID
  std::size_t maxInst{0};              // Maximum service instance count
  std::list<IdlMethod> method_list;    ///< service methods
  std::list<IdlEnum> enum_list;        ///< all enums
  using ServiceNotations = std::unordered_map<std::string, IdlServiceNotation>;
  ServiceNotations service_notations;                 ///< service notation
  ServiceLoadType loadType{ServiceLoadType::DYNAMIC}; ///< service load type
  // Validation
  operator bool() { return (!name.empty()); }
  // Add service method
  bool addMethod(IdlMethod &&method) {
    for (auto &other : method_list) {
      if (other.name == method.name) {
        return false;
      }
    }
    method_list.emplace_back(std::move(method));
    return true;
  }
};

using FileSet = std::unordered_set<std::string>;
using StructSet = std::unordered_set<std::string>;
using ServiceMap = std::unordered_map<std::string, IdlService>;
using StructList = std::list<IdlStruct>;
using EnumList = std::list<IdlEnum>;
using EnumNameSet = std::unordered_set<std::string>;
;

class Frontend;

class Parser {
  FileSet fileSet_;         ///< All .idl file
  StructSet structSet_;     ///< All struct name
  StructList structList_;   ///< All struct
  ServiceMap serviceMap_;   ///< All service
  EnumList enumList_;       ///< All enum
  EnumNameSet enumNameSet_; ///< Enum name set
  std::string error_;       ///< error string
  std::string curFile_;     ///< Current .idl file

  using TokenizerList = std::list<Tokenizer *>;
  TokenizerList tokenizerList_;      ///< All tokenizer
  Tokenizer *curTokenizer_{nullptr}; // Current tokenizer
  bool validationFailed_{false};     // Fail to validating
  Frontend *frontend_{nullptr};

public:
  ///< exe
  static std::string workingPath_;
  ///< include file search root
  static std::vector<std::string> includePathlist_;

public:
  Parser(Frontend *frontend) : frontend_(frontend) {}
  // dtor
  ~Parser();
  // Analyze file
  // @param file file name
  // @param os output stream for error
  // @param file search path
  // @retval true success
  // @retval false failure
  bool analyze(const std::string &file, std::ostream &os,
               const std::string &searchpath);
  // Validation
  // @param os output stream for error
  // @retval true success
  // @retval false failure
  bool validate(std::ostream &os);
  // Returns all IDL file
  const FileSet &getFiles() const { return fileSet_; }
  // Returns all struct
  const StructSet &getStructs() const { return structSet_; }
  // Returns all struct
  const StructList &getStructList() const { return structList_; }
  // Returns all service
  const ServiceMap &getService() const { return serviceMap_; }
  // Returns all enum
  const EnumList &getEnumList() const { return enumList_; }
  // Return error
  const std::string &getError() const { return error_; }

private:
  bool analyze_unsafe(const std::string &file, std::ostream &os,
                      const std::string &searchpath);
  // Validates all struct
  // @param os output stream
  bool validateAllStruct(std::ostream &os);
  // Validates all service
  // @param os output stream
  bool validateAllService(std::ostream &os);
  // Validates all field in struct
  // @param os output stream
  // @param field struct field
  bool validateStructField(std::ostream &os, IdlNode &field,
                           std::list<std::string> &ref);
  bool validateServiceMethod(std::ostream &os, IdlMethod &method);
  bool validateMethodArgument(std::ostream &os, IdlNode &arg);
  bool isEnum(const std::string &name);
  bool addEnum(IdlEnum &&idlEnum);
  // Start to parse an IDL file
  // @param file file name
  // @param tokenizer Tokenizer
  // @param os error output stream
  // @param file search path
  // @retval true success
  // @retval false failure
  bool parse(const std::string &file, Tokenizer &tokenizer, std::ostream &os,
             const std::string &searchpath);
  // Start to parse an IDL chunk
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param os error output stream
  // @param raiseError throw exception or not
  // @retval true success
  // @retval false failure
  bool doChunk(std::istream &is, Tokenizer &tokenizer, std::ostream &os,
               bool raiseError);
  // Start to parse an IDL struct
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param os error output stream
  void doStruct(std::istream &is, Tokenizer &tokenizer, std::ostream &os);
  // Start to parse an IDL chunk in struct or service
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param os error output stream
  // @param cur current token
  void doWrapChunk(std::istream &is, Tokenizer &tokenizer, std::ostream &os,
                   Token &cur);
  // Start to parse field in a struct
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param cur current token
  // @return a IDL node
  IdlNode doStructField(std::istream &is, Tokenizer &tokenizer, Token &cur);
  // Start to parse field in a struct
  // @param is std::istream
  // @param field struct field
  // @param tokenizer Tokenizer
  // @param cur current token
  void doFieldDefaultValue(std::istream& is, IdlNode& field, Tokenizer& tokenizer, Token& cur);
  // Start to parse an IDL service
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param os error output stream
  void doService(std::istream &is, Tokenizer &tokenizer, std::ostream &os);
  // Start to parse an IDL service notation
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param os error output stream
  IdlServiceNotation doNotation(std::istream &is, Tokenizer &tokenizer,
                                Token &cur);
  // Start to parse method in a service
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param cur current token
  // @return a IDL service method
  IdlMethod doMethod(std::istream &is, Tokenizer &tokenizer, Token &cur);
  // Start to parse return type in a method
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param cur current token
  // @return a IDL node
  IdlNode doMethodRet(std::istream &is, Tokenizer &tokenizer, Token &cur);
  // Start to parse argument in a method
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param cur current token
  // @return a IDL node
  IdlNode doMethodArg(std::istream &is, Tokenizer &tokenizer, Token &cur);
  // Start to parse single line comment
  // @param is std::istream
  // @param tokenizer Tokenizer
  void doSingleComment(std::istream &is, Tokenizer &tokenizer);
  // Start to parse multi-line comment
  // @param is std::istream
  // @param tokenizer Tokenizer
  void doMultiComment(std::istream &is, Tokenizer &tokenizer);
  // Start to parse an IDL import file
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param os error output stream
  void doImport(std::istream &is, Tokenizer &tokenizer, std::ostream &os);
  // Skip all token until a non-blank and non-comment token
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @return current token
  Token &skipBlankNoEof(std::istream &is, Tokenizer &tokenizer);
  // Skip all token until a non-comment token
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @return current token
  Token& skipNoEof(std::istream& is, Tokenizer& tokenizer);
  // Add a struct
  // @param stru struct instance
  void addStruct(IdlStruct &stru);
  // Start to parse a identifier field in struct or argument in service
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param field IDL node
  // @param withName has a name?
  void doFieldIdentifier(std::istream &is, Tokenizer &tokenizer, IdlNode &field,
                         bool withName);
  // Start to parse a sequnce field in struct or argument in service
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param field IDL node
  // @param withName has a name?
  void doFieldSeq(std::istream &is, Tokenizer &tokenizer, IdlNode &field,
                  bool withName);
  // Start to parse a set field in struct or argument in service
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param field IDL node
  // @param withName has a name?
  void doFieldSet(std::istream &is, Tokenizer &tokenizer, IdlNode &field,
                  bool withName);
  // Start to parse a dictionary field in struct or argument in service
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param field IDL node
  // @param withName has a name?
  void doFieldDict(std::istream &is, Tokenizer &tokenizer, IdlNode &field,
                   bool withName);
  // Generates token error string
  // @param token token
  // @param os output stream
  // @return error string
  std::string getTokenError(Token &token, std::ostream &os);
  // Start to parse enum in a service
  // @param is std::istream
  // @param tokenizer Tokenizer
  void doEnum(std::istream &is, Tokenizer &tokenizer);
  // Start to parse enum value in a service
  // @param is std::istream
  // @param tokenizer Tokenizer
  // @param idlEnum IDL enum
  void doEnumValue(std::istream &is, Tokenizer &tokenizer, IdlEnum &idlEnum);

  void doPreKeyword(IdlMethod &method, std::istream &is, Tokenizer &tokenizer,
                    Token &cur);

  ValueType getValueType(Token &cur);

  // Check argument name
  // @param name argument name
  bool checkArgName(const std::string &name);
};
